# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt


import frappe
from frappe import _
from frappe.exceptions import ValidationError
from frappe.model.document import Document
from frappe.model.naming import make_autoname
from frappe.utils import nowdate


class ShareDontExists(ValidationError): pass

class ShareTransfer(Document):
	def on_submit(self):
		if self.transfer_type == 'Issue':
			shareholder = self.get_company_shareholder()
			shareholder.append('share_balance', {
				'share_type': self.share_type,
				'from_no': self.from_no,
				'to_no': self.to_no,
				'rate': self.rate,
				'amount': self.amount,
				'no_of_shares': self.no_of_shares,
				'is_company': 1,
				'current_state': 'Issued'
			})
			shareholder.save()

			doc = self.get_shareholder_doc(self.to_shareholder)
			doc.append('share_balance', {
				'share_type': self.share_type,
				'from_no': self.from_no,
				'to_no': self.to_no,
				'rate': self.rate,
				'amount': self.amount,
				'no_of_shares': self.no_of_shares
			})
			doc.save()

		elif self.transfer_type == 'Purchase':
			self.remove_shares(self.from_shareholder)
			self.remove_shares(self.get_company_shareholder().name)

		elif self.transfer_type == 'Transfer':
			self.remove_shares(self.from_shareholder)
			doc = self.get_shareholder_doc(self.to_shareholder)
			doc.append('share_balance', {
				'share_type': self.share_type,
				'from_no': self.from_no,
				'to_no': self.to_no,
				'rate': self.rate,
				'amount': self.amount,
				'no_of_shares': self.no_of_shares
			})
			doc.save()

	def on_cancel(self):
		if self.transfer_type == 'Issue':
			compnay_shareholder = self.get_company_shareholder()
			self.remove_shares(compnay_shareholder.name)
			self.remove_shares(self.to_shareholder)

		elif self.transfer_type == 'Purchase':
			compnay_shareholder = self.get_company_shareholder()
			from_shareholder = self.get_shareholder_doc(self.from_shareholder)

			from_shareholder.append('share_balance', {
				'share_type': self.share_type,
				'from_no': self.from_no,
				'to_no': self.to_no,
				'rate': self.rate,
				'amount': self.amount,
				'no_of_shares': self.no_of_shares
			})

			from_shareholder.save()

			compnay_shareholder.append('share_balance', {
				'share_type': self.share_type,
				'from_no': self.from_no,
				'to_no': self.to_no,
				'rate': self.rate,
				'amount': self.amount,
				'no_of_shares': self.no_of_shares
			})

			compnay_shareholder.save()

		elif self.transfer_type == 'Transfer':
			self.remove_shares(self.to_shareholder)
			from_shareholder = self.get_shareholder_doc(self.from_shareholder)
			from_shareholder.append('share_balance', {
				'share_type': self.share_type,
				'from_no': self.from_no,
				'to_no': self.to_no,
				'rate': self.rate,
				'amount': self.amount,
				'no_of_shares': self.no_of_shares
			})
			from_shareholder.save()

	def validate(self):
		self.get_company_shareholder()
		self.basic_validations()
		self.folio_no_validation()

		if self.transfer_type == 'Issue':
			# validate share doesn't exist in company
			ret_val = self.share_exists(self.get_company_shareholder().name)
			if ret_val in ('Complete', 'Partial'):
				frappe.throw(_('The shares already exist'), frappe.DuplicateEntryError)
		else:
			# validate share exists with from_shareholder
			ret_val = self.share_exists(self.from_shareholder)
			if ret_val in ('Outside', 'Partial'):
				frappe.throw(_("The shares don't exist with the {0}")
					.format(self.from_shareholder), ShareDontExists)

	def basic_validations(self):
		if self.transfer_type == 'Purchase':
			self.to_shareholder = ''
			if not self.from_shareholder:
				frappe.throw(_('The field From Shareholder cannot be blank'))
			if not self.from_folio_no:
				self.to_folio_no = self.autoname_folio(self.to_shareholder)
			if not self.asset_account:
				frappe.throw(_('The field Asset Account cannot be blank'))
		elif (self.transfer_type == 'Issue'):
			self.from_shareholder = ''
			if not self.to_shareholder:
				frappe.throw(_('The field To Shareholder cannot be blank'))
			if not self.to_folio_no:
				self.to_folio_no = self.autoname_folio(self.to_shareholder)
			if not self.asset_account:
				frappe.throw(_('The field Asset Account cannot be blank'))
		else:
			if not self.from_shareholder or not self.to_shareholder:
				frappe.throw(_('The fields From Shareholder and To Shareholder cannot be blank'))
			if not self.to_folio_no:
				self.to_folio_no = self.autoname_folio(self.to_shareholder)
		if not self.equity_or_liability_account:
				frappe.throw(_('The field Equity/Liability Account cannot be blank'))
		if self.from_shareholder == self.to_shareholder:
			frappe.throw(_('The seller and the buyer cannot be the same'))
		if self.no_of_shares != self.to_no - self.from_no + 1:
			frappe.throw(_('The number of shares and the share numbers are inconsistent'))
		if not self.amount:
			self.amount = self.rate * self.no_of_shares
		if self.amount != self.rate * self.no_of_shares:
			frappe.throw(_('There are inconsistencies between the rate, no of shares and the amount calculated'))

	def share_exists(self, shareholder):
		doc = self.get_shareholder_doc(shareholder)
		for entry in doc.share_balance:
			if entry.share_type != self.share_type or \
				entry.from_no > self.to_no or \
				entry.to_no < self.from_no:
				continue # since query lies outside bounds
			elif entry.from_no <= self.from_no and entry.to_no >= self.to_no: #both inside
				return 'Complete' # absolute truth!
			elif entry.from_no <= self.from_no <= self.to_no:
				return 'Partial'
			elif entry.from_no <= self.to_no <= entry.to_no:
				return 'Partial'

		return 'Outside'

	def folio_no_validation(self):
		shareholder_fields = ['from_shareholder', 'to_shareholder']
		for shareholder_field in shareholder_fields:
			shareholder_name = self.get(shareholder_field)
			if not shareholder_name:
				continue
			doc = self.get_shareholder_doc(shareholder_name)
			if doc.company != self.company:
				frappe.throw(_('The shareholder does not belong to this company'))
			if not doc.folio_no:
				doc.folio_no = self.from_folio_no \
					if (shareholder_field == 'from_shareholder') else self.to_folio_no
				doc.save()
			else:
				if doc.folio_no and doc.folio_no != (self.from_folio_no if (shareholder_field == 'from_shareholder') else self.to_folio_no):
					frappe.throw(_('The folio numbers are not matching'))

	def autoname_folio(self, shareholder, is_company=False):
		if is_company:
			doc = self.get_company_shareholder()
		else:
			doc = self.get_shareholder_doc(shareholder)
		doc.folio_no = make_autoname('FN.#####')
		doc.save()
		return doc.folio_no

	def remove_shares(self, shareholder):
		# query = {'from_no': share_starting_no, 'to_no': share_ending_no}
		# Shares exist for sure
		# Iterate over all entries and modify entry if in entry
		doc = frappe.get_doc('Shareholder', shareholder)
		current_entries = doc.share_balance
		new_entries = []

		for entry in current_entries:
			# use spaceage logic here
			if entry.share_type != self.share_type or \
				entry.from_no > self.to_no or \
				entry.to_no < self.from_no:
				new_entries.append(entry)
				continue # since query lies outside bounds
			elif entry.from_no <= self.from_no and entry.to_no >= self.to_no:
				#split
				if entry.from_no == self.from_no:
					if entry.to_no == self.to_no:
						pass #nothing to append
					else:
						new_entries.append(self.return_share_balance_entry(self.to_no+1, entry.to_no, entry.rate))
				else:
					if entry.to_no == self.to_no:
						new_entries.append(self.return_share_balance_entry(entry.from_no, self.from_no-1, entry.rate))
					else:
						new_entries.append(self.return_share_balance_entry(entry.from_no, self.from_no-1, entry.rate))
						new_entries.append(self.return_share_balance_entry(self.to_no+1, entry.to_no, entry.rate))
			elif entry.from_no >= self.from_no and entry.to_no <= self.to_no:
				# split and check
				pass #nothing to append
			elif self.from_no <= entry.from_no <= self.to_no and entry.to_no >= self.to_no:
				new_entries.append(self.return_share_balance_entry(self.to_no+1, entry.to_no, entry.rate))
			elif self.from_no <= entry.to_no <= self.to_no and entry.from_no <= self.from_no:
				new_entries.append(self.return_share_balance_entry(entry.from_no, self.from_no-1, entry.rate))
			else:
				new_entries.append(entry)

		doc.share_balance = []
		for entry in new_entries:
			doc.append('share_balance', entry)
		doc.save()

	def return_share_balance_entry(self, from_no, to_no, rate):
		# return an entry as a dict
		return {
			'share_type'   : self.share_type,
			'from_no'	   : from_no,
			'to_no'		   : to_no,
			'rate'		   : rate,
			'amount'	   : self.rate * (to_no - from_no + 1),
			'no_of_shares' : to_no - from_no + 1
		}

	def get_shareholder_doc(self, shareholder):
		# Get Shareholder doc based on the Shareholder name
		if shareholder:
			query_filters = {'name': shareholder}

		name = frappe.db.get_value('Shareholder', {'name': shareholder}, 'name')

		return frappe.get_doc('Shareholder', name)

	def get_company_shareholder(self):
		# Get company doc or create one if not present
		company_shareholder = frappe.db.get_value('Shareholder',
			{
				'company': self.company,
				'is_company': 1
			}, 'name')

		if company_shareholder:
			return frappe.get_doc('Shareholder', company_shareholder)
		else:
			shareholder = frappe.get_doc({
					'doctype': 'Shareholder',
					'title': self.company,
					'company': self.company,
					'is_company': 1
				})
			shareholder.insert()

			return shareholder

@frappe.whitelist()
def make_jv_entry( company, account, amount, payment_account,\
	credit_applicant_type, credit_applicant, debit_applicant_type, debit_applicant):
	journal_entry = frappe.new_doc('Journal Entry')
	journal_entry.voucher_type = 'Journal Entry'
	journal_entry.company = company
	journal_entry.posting_date = nowdate()
	account_amt_list = []

	account_amt_list.append({
		"account": account,
		"debit_in_account_currency": amount,
		"party_type": debit_applicant_type,
		"party": debit_applicant,
		})
	account_amt_list.append({
		"account": payment_account,
		"credit_in_account_currency": amount,
		"party_type": credit_applicant_type,
		"party": credit_applicant,
		})
	journal_entry.set("accounts", account_amt_list)
	return journal_entry.as_dict()
